package com.linkin.homepage.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;

import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.recyclerview.widget.RecyclerView;

import com.google.android.material.appbar.AppBarLayout;
import com.linkin.homepage.base.util.ReflectUtils;

import java.lang.reflect.Method;


public class FixedBehavior extends AppBarLayout.Behavior {

    private static final int INVALID_POINTER = -1;

    private boolean mIsBeingDragged;
    private int mActivePointerId = INVALID_POINTER;
    private int mLastMotionY;
    private int mTouchSlop = -1;
    private VelocityTracker mVelocityTracker;

    private static Method sScrollMethod;
    private static Method sMethodFling;
    private static Method sMethodGetDownNestedPreScrollRange;

    public FixedBehavior() {
    }

    public FixedBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
                                  View target, int dx, int dy, int[] consumed, int type) {
        if (dy != 0) {
            int min = getMinOffset(coordinatorLayout, child);
            int max = 0;
            if (dy < 0) {
                if (min == -child.getTotalScrollRange()) {
                    max = min + getDownNestedPreScrollRange(child);
                }
            }

            if (min != max) {
                consumed[1] = scroll(coordinatorLayout, child, dy, min, 0);
            }
        }
    }

    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
                               View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
                               int type) {
        if (dyUnconsumed < 0) {
            // If the scrolling view is scrolling down but not consuming, it's probably be at
            // the top of it's content
            scroll(coordinatorLayout, child, dyUnconsumed,
                    getMinOffset(coordinatorLayout, child), 0);
        }
    }


    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
        if (mTouchSlop < 0) {
            mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop();
        }

        final int action = ev.getAction();

        // Shortcut since we're being dragged
        if (action == MotionEvent.ACTION_MOVE && mIsBeingDragged) {
            return true;
        }

        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN: {
                boolean stopped = stopScroll(parent);

                mIsBeingDragged = false;
                final int x = (int) ev.getX();
                final int y = (int) ev.getY();
                if (parent.isPointInChildBounds(child, x, y)) {
                    if (stopped) {
                        mIsBeingDragged = true;
                    }
                    mLastMotionY = y;
                    mActivePointerId = ev.getPointerId(0);
                    ensureVelocityTracker();
                }
                break;
            }

            case MotionEvent.ACTION_MOVE: {
                final int activePointerId = mActivePointerId;
                if (activePointerId == INVALID_POINTER) {
                    // If we don't have a valid id, the touch down wasn't on content.
                    break;
                }
                final int pointerIndex = ev.findPointerIndex(activePointerId);
                if (pointerIndex == -1) {
                    break;
                }

                final int y = (int) ev.getY(pointerIndex);
                final int yDiff = Math.abs(y - mLastMotionY);
                if (yDiff > mTouchSlop) {
                    mIsBeingDragged = true;
                    mLastMotionY = y;
                }
                break;
            }

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP: {
                mIsBeingDragged = false;
                mActivePointerId = INVALID_POINTER;
                if (mVelocityTracker != null) {
                    mVelocityTracker.recycle();
                    mVelocityTracker = null;
                }
                break;
            }
        }

        if (mVelocityTracker != null) {
            mVelocityTracker.addMovement(ev);
        }

        return mIsBeingDragged;
    }

    @Override
    public boolean onTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
        if (mTouchSlop < 0) {
            mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop();
        }

        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN: {
                stopScroll(parent);

                final int x = (int) ev.getX();
                final int y = (int) ev.getY();

                if (parent.isPointInChildBounds(child, x, y)) {
                    mLastMotionY = y;
                    mActivePointerId = ev.getPointerId(0);
                    ensureVelocityTracker();
                } else {
                    return false;
                }
                break;
            }

            case MotionEvent.ACTION_MOVE: {
                final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
                if (activePointerIndex == -1) {
                    return false;
                }

                final int y = (int) ev.getY(activePointerIndex);
                int dy = mLastMotionY - y;

                if (!mIsBeingDragged && Math.abs(dy) > mTouchSlop) {
                    mIsBeingDragged = true;
                    if (dy > 0) {
                        dy -= mTouchSlop;
                    } else {
                        dy += mTouchSlop;
                    }
                }

                if (mIsBeingDragged) {
                    mLastMotionY = y;
                    scroll(parent, child, dy, getMinOffset(parent, child), 0);
                }
                break;
            }

            case MotionEvent.ACTION_UP:
                if (mVelocityTracker != null) {
                    mVelocityTracker.addMovement(ev);
                    mVelocityTracker.computeCurrentVelocity(1000);
                    float yVelocity = mVelocityTracker.getYVelocity(mActivePointerId);
                    RecyclerView recyclerView = getRecyclerView(parent);
                    if (recyclerView != null
                            && recyclerView.getHeight() > 0
                            && recyclerView.getVisibility() == View.VISIBLE) {
                        recyclerView.fling(0, (int)-yVelocity);
                    } else {
                        fling(parent, child, getMinOffset(parent, child), 0, yVelocity);
                    }
                }

            case MotionEvent.ACTION_CANCEL: {
                mIsBeingDragged = false;
                mActivePointerId = INVALID_POINTER;
                if (mVelocityTracker != null) {
                    mVelocityTracker.recycle();
                    mVelocityTracker = null;
                }
                break;
            }
        }

        if (mVelocityTracker != null) {
            mVelocityTracker.addMovement(ev);
        }

        return true;
    }

    public int getMinOffset(CoordinatorLayout parent, AppBarLayout child) {
        int minOffset = getMinOffsetTmp(parent, child);
        int currentOffset = getTopAndBottomOffset();
        if (currentOffset < minOffset) {
            setTopAndBottomOffset(minOffset);
        }
        return minOffset;
    }

    private int getMinOffsetTmp(CoordinatorLayout parent, AppBarLayout child) {
        RecyclerView recyclerView = getRecyclerView(parent);
        if (recyclerView == null) {
            return -child.getTotalScrollRange();
        }

        int height = parent.getHeight();
        int recyclerViewHeight = recyclerView.getHeight();
        int totalScrollRange = child.getTotalScrollRange();
        if (recyclerView.getVisibility() != View.VISIBLE || recyclerViewHeight == 0) {
            if (height < totalScrollRange) {
                return height - totalScrollRange;
            }
            return 0;
        }

        if (recyclerViewHeight < height) {
            if (height < (totalScrollRange + recyclerViewHeight)) {
                return height - (totalScrollRange + recyclerViewHeight);
            }
            return 0;
        }
        return -totalScrollRange;
    }

    private void ensureVelocityTracker() {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
    }

    private int scroll(
            CoordinatorLayout coordinatorLayout, AppBarLayout header, int dy, int minOffset, int maxOffset) {
        int ret = ReflectUtils.invokeInt(this, sScrollMethod,
                coordinatorLayout, header, dy, minOffset, maxOffset);
        return ret;
    }

    private void fling(CoordinatorLayout coordinatorLayout, AppBarLayout layout, int minOffset,
                       int maxOffset, float velocityY) {
        ReflectUtils.invoke(this, sMethodFling,
                coordinatorLayout, layout, minOffset, maxOffset, velocityY);
    }

    private int getDownNestedPreScrollRange(AppBarLayout appBarLayout) {
        int ret = ReflectUtils.invokeInt(appBarLayout, sMethodGetDownNestedPreScrollRange);
        return ret;
    }

    private RecyclerView getRecyclerView(CoordinatorLayout parent) {
        if (parent != null) {
            int count = parent.getChildCount();
            for (int i = count - 1; i >= 0; i--) {
                View view = parent.getChildAt(i);
                if (view instanceof RecyclerView) {
                    return (RecyclerView)view;
                }
            }
        }
        return null;
    }

    private boolean stopScroll(CoordinatorLayout parent) {
        RecyclerView recyclerView = getRecyclerView(parent);
        if (recyclerView != null
                && recyclerView.getHeight() > 0
                && recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_SETTLING) {
            recyclerView.stopScroll();
            return true;
        }
        return false;
    }

    static {
        try {
            sScrollMethod = ReflectUtils.getMethod(
                    Class.forName("android.support.design.widget.HeaderBehavior"),
                    "scroll",
                    CoordinatorLayout.class, View.class, int.class, int.class, int.class);
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            sMethodFling = ReflectUtils.getMethod(
                    Class.forName("android.support.design.widget.HeaderBehavior"),
                    "fling",
                    CoordinatorLayout.class, View.class, int.class, int.class, float.class);
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            sMethodGetDownNestedPreScrollRange = ReflectUtils.getMethod(
                    AppBarLayout.class,
                    "getDownNestedPreScrollRange");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
